package com.chenfan.ccp.util.start;

import com.chenfan.ccp.util.sign.ConfigPropertiesUtil;
import com.chenfan.ccp.util.tool.ScannerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.Query;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ApplicationContextUtil {
    private static final Logger logger = LoggerFactory.getLogger(ApplicationContextUtil.class);
    private static final String EMPTY = "";
    private static final String DEV_PROFILE = "dev";
    private static final String PROD_PROFILE = "prod";
    private static final String TEST_PROFILE = "test";
    private static final String YAML_CONFIG = ".yml";
    private static final String PROPERTIES_CONFIG = ".properties";
    private static final String EL_PATTER = "\\$\\{(.*)}";
    private static final String CONFIG_MULTIPLE_SUFFIX = "\\*";
    private static final String DEFAULT_VAL_LINK = ":";
    private static final String CONFIG_ENVIRON_PROPERTIES = "application-%s{0}";
    private static final String CLASSPATH_CONFIG_ENVIRON_PROPERTIES = "classpath*:/" + CONFIG_ENVIRON_PROPERTIES;
    private static final String SPRING_APPLICATION_NAME = "spring.application.name";
    private static final String SPRING_PROFILE_NAME = "spring.profiles.active";
    private static ApplicationContext context;
    private static String serviceName = EMPTY;
    private static String PROFILE = DEV_PROFILE;

    private ApplicationContextUtil() {

    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static synchronized void setContext(ApplicationContext context) {
        ApplicationContextUtil.context = context;
    }

    public static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }

    public static String getServiceName() {
        if (serviceName.isEmpty()) {
            synchronized (ApplicationContextUtil.class) {
                Environment environment = getContext().getBean(Environment.class);
                Objects.requireNonNull(context);
                serviceName = environment.getProperty(SPRING_APPLICATION_NAME);
                if (serviceName == null) {
                    serviceName = System.getProperty(SPRING_APPLICATION_NAME);
                }
            }

        }
        return serviceName;
    }

    /**
     * 启动
     *
     * @param args 启动指定参数
     */

    public static void run(String... args) {

        run(null, args);


    }

    /**
     * 启动
     *
     * @param docPackageName 文档扫描包名称
     * @param args           启动指定参数
     */

    public static void run(String docPackageName, String... args) {
        //获取设置启动的环境
        setProdProfile(args);

        boolean openDocFlag = false;
        if (docPackageName != null && !docPackageName.isEmpty()) {
            try {
                ScannerUtil.getDoc(docPackageName);
                openDocFlag = true;
            } catch (IOException e) {

            }
        }


        //获取启动的类
        Class startMain = getStartMain();

        //加载配置
        loadProperties(startMain);

        //启动程序
        context = SpringApplication.run(startMain, args);

        //执行程序
        executeTaskAndGetPort(openDocFlag);


    }

    public static boolean isConsoleStart() {
        return false;
    }

    private static Class getStartMain() {

        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

        String currentClassName = stackTrace[isConsoleStart() ? stackTrace.length - 1 : 4].getClassName();

        try {
            return Class.forName(currentClassName);
        } catch (ClassNotFoundException var3) {
            return null;
        }
    }

    /**
     * 执行任务并且获取端口
     *
     * @param openDoc
     */
    private static void executeTaskAndGetPort(Boolean openDoc) {
        String port;
        try {
            port = ManagementFactory.getPlatformMBeanServer().queryNames(new ObjectName("*:type=Connector,*"),
                    Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"))).iterator().next().getKeyProperty("port");
        } catch (MalformedObjectNameException | NoSuchElementException e) {
            Environment environment = context.getEnvironment();
            port = environment.getProperty("server.port", "8080");
        }

        Environment environment = getContext().getBean(Environment.class);
        String loggerPath = environment.getProperty("logging.file.path", "./log/");
        logger.info("======================================================================================");
        logger.info("                                                                                      ");
        logger.info(String.format("................................服务启动成功,端口为%s................................", port));
        logger.info(String.format("................................日志保存路径为: %s................................", loggerPath));
        logger.info("                                                                                      ");
        logger.info("======================================================================================");


        //执行启动后的任务


        if (openDoc && isConsoleStart()) {

            logger.info("api文档生成成功 访问地址:http://" + getLocalIp() + ":" + port);

        }
    }

    /**
     * 获取本机Ip
     *
     * @return
     */
    public static String getLocalIp() {
        String ret = EMPTY;
        try {
            Enumeration<NetworkInterface> netInterfaces = NetworkInterface
                    .getNetworkInterfaces();
            InetAddress ip;
            while (netInterfaces.hasMoreElements()) {
                NetworkInterface ni = netInterfaces.nextElement();
                Enumeration<InetAddress> address = ni.getInetAddresses();
                while (address.hasMoreElements()) {
                    ip = address.nextElement();
                    if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
                            && ip.getHostAddress().indexOf(":") == -1) {
                        return ip.getHostAddress();
                    } else if (ip.isSiteLocalAddress()
                            && !ip.isLoopbackAddress()
                            && ip.getHostAddress().indexOf(":") == -1) {
                        ret = ip.getHostAddress();
                    }
                }
            }
        } catch (Exception e) {
        }
        return ret;
    }

    public static boolean isProd() {
        return getProfile().equalsIgnoreCase(PROD_PROFILE);
    }

    public static boolean isDev() {
        return getProfile().equalsIgnoreCase(DEV_PROFILE);
    }

    public static boolean isTest() {
        return getProfile().equalsIgnoreCase(TEST_PROFILE);
    }

    public static String getProfile() {
        if (PROFILE.isEmpty()) {
            synchronized (ApplicationContextUtil.class) {
                if (getContext() == null) {
                    return PROFILE;
                }
                Environment environment = getContext().getBean(Environment.class);
                PROFILE = environment.getProperty(SPRING_PROFILE_NAME, DEV_PROFILE);
            }

        }
        return PROFILE;
    }

    public static void setProdProfile(String... args) {
        //启动参数后
        Pattern profilePattern = Pattern.compile("--" + SPRING_PROFILE_NAME + "=(\\w+)");
        for (String arg : args) {
            Matcher matcher = profilePattern.matcher(arg);
            if (matcher.find()) {
                PROFILE = matcher.group(1);
                return;
            }
        }

        //环境变量--增加环境变量的适配
        Map<String, String> systemEnv = System.getenv();
        Properties systemProperties = System.getProperties();
        systemProperties.putAll(systemEnv);

        for (Object obj : systemProperties.values()) {
            String val = obj.toString().toLowerCase();
            if (DEV_PROFILE.equalsIgnoreCase(val)
                    || TEST_PROFILE.equalsIgnoreCase(val)
                    || PROD_PROFILE.equalsIgnoreCase(val)) {
                PROFILE = val.toLowerCase();
                break;
            }
        }


    }


    /**
     * 获取环境配置
     *
     * @param fileName  文件的名称
     * @param localLoad 是否是本地加载
     * @return
     */
    private static Properties getProperties(String fileName, boolean localLoad) {
        Properties properties = new Properties();
        if (localLoad) {

            Resource[] resources;


            try {

                YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();

                resources = new PathMatchingResourcePatternResolver().getResources(
                        MessageFormat.format(fileName, YAML_CONFIG)

                );

                yaml.setResources(resources);


                properties = yaml.getObject();


            } catch (Exception e) {


            }


            try {

                TreeMap<String, Resource> resourceTreeMap = new TreeMap<>();

                resources = new PathMatchingResourcePatternResolver().getResources(
                        MessageFormat.format(fileName, PROPERTIES_CONFIG)
                );
                if (resources != null) {
                    for (Resource resource : resources) {
                        resourceTreeMap.put(resource.getFilename(), resource);
                    }
                }


                //重新排序
                for (Resource resourceVal : resourceTreeMap.values()) {
                    //已经存在的key不会替换
                    properties.load(resourceVal.getInputStream());
                }


            } catch (Exception e) {


            }


        } else {

            properties = ConfigPropertiesUtil.getProjectProperties();


        }


        return properties;

    }

    /**
     * 加载环境配置
     */
    private static void loadProperties(Class source) {
        try {

            if (getFirstAndLowerClassName(source).equalsIgnoreCase(System.getProperty(SPRING_APPLICATION_NAME))) {
                return;
            }


            //加载环境配置
            Properties properties = getProperties(String.format(CLASSPATH_CONFIG_ENVIRON_PROPERTIES, getProfile()), true);


//            //加入git读取配置
//            Properties gitProperties = getProperties(null, false);
//            if (gitProperties != null) {
//                properties.putAll(gitProperties);
//            }


            //本地环境加入开发环境,加入开发环境的配置，读取的配置是优先级最高
            if (isDev()) {

                properties.putAll(getProperties(String.format(CLASSPATH_CONFIG_ENVIRON_PROPERTIES,
                        DEV_PROFILE).replaceAll(CONFIG_MULTIPLE_SUFFIX, EMPTY), true));

            }


            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                Object key = entry.getKey();
                Object value = entry.getValue();
                if (key == null || value == null) {
                    continue;
                }
                //判定是否是通过参数设定的属性
                if (System.getProperty(key.toString()) != null) {
                    continue;
                }

                //进行占位符的解析
                System.setProperty(key.toString(), parseEl(value.toString(), properties));

            }


//            System.setProperty(SPRING_APPLICATION_NAME, getFirstAndLowerClassName(source));

        } catch (Exception e) {
            logger.error("加载环境配置出错", e);

        }

    }

    /**
     * 解析获得第一个大写截止并且全部小写的类名
     *
     * @param className
     * @return
     */
    private static String getFirstAndLowerClassName(Class className) {
        char[] chars = className.getSimpleName().toCharArray();
        StringBuilder builder = new StringBuilder();
        builder.append(Character.toLowerCase(chars[0]));
        for (int i = 1; i < chars.length; i++) {
            if (Character.isUpperCase(chars[i])) {
                break;
            }
            builder.append(chars[i]);
        }
        return builder.toString();
    }

    /**
     * 解析el
     *
     * @param elStr      el的字符串
     * @param properties 所有key对应的properties
     * @return
     */
    public static String parseEl(String elStr, Properties properties) {
        StringBuilder sb = new StringBuilder();

        StringBuilder newLines = new StringBuilder();


        char[] chars = elStr.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            switch (chars[i]) {
                case '#':
                case '$':
                    if (i < chars.length - 1 && chars[i + 1] == '{') {
                        newLines.append(sb.toString());
                        sb.setLength(0);
                    }
                    sb.append(chars[i]);
                    break;
                case '}':
                    sb.append('}');
                    if (sb.length() > 1 && sb.charAt(1) == '{') {
                        //解析${key:defaults} 当key不存在则用默认值
                        Matcher matcher = Pattern.compile(EL_PATTER).matcher(sb.toString());
                        if (matcher.find()) {

                            String resultLine = matcher.group(1);

                            if (resultLine.contains(DEFAULT_VAL_LINK)) {
                                String[] split = resultLine.split(DEFAULT_VAL_LINK);

                                String _key = split[0];
                                String _defaultVal;
                                if (split.length != 1) {
                                    _defaultVal = split[1];
                                } else {
                                    _defaultVal = EMPTY;
                                }

                                resultLine = properties.getProperty(_key);
                                if (resultLine == null) {
                                    resultLine = _defaultVal;
                                }

                            } else {
                                resultLine = properties.getProperty(resultLine);
                            }

                            if (resultLine != null) {
                                newLines.append(resultLine);
                            }


                        }
                        sb.setLength(0);
                    }
                    break;
                default:
                    sb.append(chars[i]);
                    break;
            }
        }

        if (sb.length() > 0) {
            newLines.append(sb.toString());
        } else {
            return null;
        }
        return newLines.toString();
    }


}
